package tictactoe.multiplayer.client;

import tictactoe.Player;
import tictactoe.exceptions.network.ConnectionException;
import tictactoe.exceptions.network.NetworkException;
import tictactoe.multiplayer.Message;
import tictactoe.util.Constants;
import java.awt.Point;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

public abstract class Client extends Thread {
    private static final int PORT = Constants.PORT;
    private static final String DEFAULT_SERVER = "localhost";

    private Socket socket;
    private PrintWriter out;
    private BufferedReader in;

    private Player player;
    private boolean connected = false;

    public Client(String server) throws NetworkException {
        try {
            socket = new Socket(server, PORT);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream());
        } catch (UnknownHostException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            throw new ConnectionException(ex);
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            throw new ConnectionException(ex);
        }
    }

    public Client() throws NetworkException {
        this(DEFAULT_SERVER);
    }

    public boolean isConnected() {
        return connected;
    }

    public Player getPlayer() {
        return player;
    }

    public void closeClient() {
        terminateConnection();
    }
    
    public void connectClient(Player p) {
        if (!connected) {
            connected = true;
            start();
        }
        this.player = p;
        sendConnect(player);
    }

    public void askForPlayers() {
        if (!connected) {
            connected = true;
            start();
        }
        sendGetPlayers();
    }

    public void startGameWith(Player player) {
        if (connected) {
            sendPlayWith(player);
        } else {
            //TODO Exception
        }
    }

    public void acceptChallenge(Player whose) {
        if (connected) {
            sendAccept(whose);
        } else {
            //TODO Exception
        }
    }

    public void move(Point p) {
        if (connected) {
            sendMove(p);
        } else {
            //TODO Exception
        }
    }

    public void disconnect() {
        terminateConnection();
    }
    
    @Override
    public void run() {
        while (connected) {
            String s = "";
            try {
                s = in.readLine();
            } catch (IOException ex) {
                Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            }

            final String msg = s;
            new Thread(new Runnable() {
                public void run() {
                   receive(msg);
                }
            }).start();
        }
    }

    // <editor-fold defaultstate="collapsed" desc="abstract method to implement">
    public abstract void connectionAcknowledged();

    public abstract void playersSetReceived(Set<Player> players);

    public abstract void playRequestReceived(Player from);

    public abstract void requestAccepted(Player from);

    public abstract void moveReceived(Point move);

    public abstract void oponentDisconnected();

    public abstract void serverShuttingDown();

    public abstract void exceptionReceived(String message);

    public abstract void receivedMessage(String msg);
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="client -> server">
    private void send(String msg) {
        out.println(msg);
        out.flush();
    }

    private void sendConnect(Player p) {
        String msg = Message.CONNECT + " " + p.getName();

        send(msg);
    }

    private void sendGetPlayers() {
        send(Message.GET_PLAYERS);
    }

    private void sendPlayWith(Player p) {
        String msg = Message.PLAY_WITH + " " + p.getName();

        send(msg);
    }

    private void sendAccept(Player p) {
        String msg = Message.ACCEPT + " " + p.getName();

        send(msg);
    }

    private void sendMove(Point p) {
        String msg = Message.MOVE + " " + p.x + "," + p.y;

        send(msg);
    }

    private void sendDisconnect() {
        send(Message.DISCONNECT);
    }
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="server -> client">
    private void receive(String msg) {
        receivedMessage(msg);
        if (msg == null) {
            //TODO Exception
            return;
        }

        msg = msg.trim().toLowerCase();

        if (msg.startsWith(Message.CONNECTED)) {
            receiveConnected();
        }

        if (msg.startsWith(Message.PLAYERS)) {
            if (msg.length() > Message.PLAYERS.length() + 1) {
                receivePlayers(msg.substring(Message.PLAYERS.length() + 1));
            }
        }

        if (msg.startsWith(Message.WANT_PLAY)) {
            receiveWantPlay(msg.substring(Message.WANT_PLAY.length() + 1));
        }

        if (msg.startsWith(Message.ACCEPTED)) {
            receiveAccepted(msg.substring(Message.ACCEPTED.length() + 1));
        }

        if (msg.startsWith(Message.OTHER_MOVE)) {
            receiveOtherMove(msg.substring(Message.OTHER_MOVE.length() + 1));
        }

        if (msg.startsWith(Message.OTHER_DISCONNECTED)) {
            oponentDisconnected();
        }

        if (msg.startsWith(Message.SERVER_DOWN)) {
            receiveServerDown();
        }

        if (msg.startsWith(Message.EXCEPTION)) {
            exceptionReceived(msg.substring(Message.EXCEPTION.length() + 1));
        }
    }

    private void receiveConnected() {
        connected = true;

        connectionAcknowledged();
    }

    private void receivePlayers(String players) {
        String names[] = players.split(Constants.PLAYER_SEPARATOR);

        Set<Player> playersSet = new HashSet<Player>();
        for (int i = 0; i < names.length; i++) {
            playersSet.add(new Player(names[i], Player.TYPE_REMOTE));
        }

        playersSetReceived(playersSet);
    }

    private void receiveWantPlay(String name) {
        Player p = new Player(name, Player.TYPE_REMOTE);

        playRequestReceived(p);
    }

    private void receiveAccepted(String name) {
        Player p = new Player(name, Player.TYPE_REMOTE);

        requestAccepted(p);
    }

    private void receiveOtherMove(String point) {
        System.out.println("7. receiveOtherMove");
        String coords[] = point.split(",");
        Point p = new Point(Integer.parseInt(coords[0]), Integer.parseInt(coords[1]));

        moveReceived(p);
    }

    private void receiveServerDown() {
        terminateConnection();
        
        serverShuttingDown();
    }
    // </editor-fold>

    private void terminateConnection() {
        sendDisconnect();
        connected = false;
        interrupt();
        
        try {
            if (in != null) in.close();
            if (out != null) out.close();
            if (socket != null) socket.close();
        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
            //TODO Exception
        }
    }
}
